﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Forms;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using ARCam3D.Imaging;
using ARCam3D.Transform;

namespace ARCam3D.UI.Graphics
{
    using GDIColor = System.Drawing.Color;
    using XNAColor = Microsoft.Xna.Framework.Color;

    public partial class MainGraphicsControl : UserControl, IGraphicsDeviceService
    {
        private double widthToHeight = 1;

        private GraphicsDevice graphicsDevice;
        private PresentationParameters graphicsPresentationParameters;
        private ContentManager contentManager = GraphicsUtils.ContentManager;
        private SpriteBatch spriteBatch;

        private RGBImage cameraImage;
        private Model model;
        private MatrixTransform3D markerTransform;

        private Matrix viewMatrix;
        private Matrix projectionMatrix;

        public event EventHandler<EventArgs> DeviceCreated;
        public event EventHandler<EventArgs> DeviceDisposing;
        public event EventHandler<EventArgs> DeviceReset;
        public event EventHandler<EventArgs> DeviceResetting;

        public MainGraphicsControl()
        {
            InitializeComponent();
        }

        public GraphicsDevice GraphicsDevice
        {
            get { return graphicsDevice; }
        }

        public void SetCameraImage(RGBImage image)
        {
            cameraImage = image;
            if (cameraImage != null)
            {
                widthToHeight = (double)cameraImage.Width / cameraImage.Height;
            }
            else
            {
                widthToHeight = 1;
            }
            resizeGraphicsPanel();
        }

        public void SetModel(string modelName)
        {
            try
            {
                model = GraphicsUtils.ContentManager.Load<Model>(modelName);
            }
            catch
            {
                model = null;
            }
        }

        public void SetMarkerTransform(MatrixTransform3D transform)
        {
            markerTransform = transform;
        }

        public Bitmap GetImage()
        {
            lock (this)
            {
                if (cameraImage == null) return null;

                resetGraphicsDevice();

                RenderTarget2D renderTarget = new RenderTarget2D(
                    graphicsDevice,
                    graphicsDevice.PresentationParameters.BackBufferWidth,
                    graphicsDevice.PresentationParameters.BackBufferHeight,
                    true, SurfaceFormat.Color, DepthFormat.Depth24
                );

                
                graphicsDevice.SetRenderTarget(renderTarget);

                draw();

                graphicsDevice.SetRenderTarget(null);

                return GraphicsUtils.GetBitmapFromTexture(renderTarget);
            }
        }

        private void MainGraphicsControl_Load(object sender, EventArgs e)
        {
            
            resizeGraphicsPanel();

            if (!this.DesignMode)
            {
                Application.Idle += delegate { graphicsPanel.Invalidate(); };
                ServiceProvider.Instance.AddService<IGraphicsDeviceService>(this);

                graphicsPresentationParameters = new PresentationParameters();
                graphicsPresentationParameters.BackBufferWidth = graphicsPanel.ClientSize.Width;
                graphicsPresentationParameters.BackBufferHeight = graphicsPanel.ClientSize.Height;
                graphicsPresentationParameters.BackBufferFormat = SurfaceFormat.Color;
                graphicsPresentationParameters.DepthStencilFormat = DepthFormat.Depth24;
                graphicsPresentationParameters.DeviceWindowHandle = graphicsPanel.Handle;
                graphicsPresentationParameters.RenderTargetUsage = RenderTargetUsage.DiscardContents;
                graphicsPresentationParameters.IsFullScreen = false;

                graphicsDevice = new GraphicsDevice(
                    GraphicsAdapter.DefaultAdapter,
                    GraphicsProfile.HiDef,
                    graphicsPresentationParameters
                );

                if (DeviceCreated != null) DeviceCreated(this, EventArgs.Empty);

                spriteBatch = new SpriteBatch(graphicsDevice);

                viewMatrix = Matrix.CreateLookAt(
                    new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 1, 0)
                );
                projectionMatrix = Matrix.CreatePerspective(0.008f, 0.006f, 0.01f, 1000f);
            }
        }

        private void graphicsPanel_Paint(object sender, PaintEventArgs e)
        {
            if (graphicsDevice == null) return;

            lock (this)
            {
                draw();

                graphicsDevice.Present();
            }
        }

        private void MainGraphicsControl_Resize(object sender, EventArgs e)
        {
            if (this.ParentForm != null && this.ParentForm.WindowState != FormWindowState.Minimized)
            {
                resizeGraphicsPanel();
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && graphicsDevice != null)
            {
                if (DeviceDisposing != null) DeviceDisposing(this, EventArgs.Empty);

                graphicsDevice.Dispose();
                graphicsDevice = null;
            }

            base.Dispose(disposing);
        }

        private void resizeGraphicsPanel()
        {
            if ((double)this.Width / (double)this.Height > widthToHeight)
            {
                graphicsPanel.Height = this.Height;
                graphicsPanel.Width = (int)(widthToHeight * graphicsPanel.Height);
                graphicsPanel.Top = 0;
                graphicsPanel.Left = (this.Width - graphicsPanel.Width) / 2;
            }
            else
            {
                graphicsPanel.Width = this.Width;
                graphicsPanel.Height = (int)(graphicsPanel.Width / widthToHeight);
                graphicsPanel.Left = 0;
                graphicsPanel.Top = (this.Height - graphicsPanel.Height) / 2;
            }

            if (graphicsDevice != null)
            {
                resetGraphicsDevice();
            }
        }

        private void resetGraphicsDevice()
        {
            if (DeviceResetting != null) DeviceResetting(this, EventArgs.Empty);

            graphicsPresentationParameters.BackBufferWidth = Math.Max(1, graphicsPanel.ClientSize.Width);
            graphicsPresentationParameters.BackBufferHeight = Math.Max(1, graphicsPanel.ClientSize.Height);
            graphicsDevice.Reset(graphicsPresentationParameters);

            if (DeviceReset != null) DeviceReset(this, EventArgs.Empty);

            //graphicsPanel.Invalidate();
        }

        private void draw()
        {
            graphicsDevice.Clear(XNAColor.Black);

            Viewport viewport = new Viewport();
            viewport.X = 0;
            viewport.Y = 0;
            viewport.Width = graphicsPanel.ClientSize.Width;
            viewport.Height = graphicsPanel.ClientSize.Height;
            viewport.MinDepth = 0.01f;
            viewport.MaxDepth = 1f;
            graphicsDevice.Viewport = viewport;

            drawImage();
            drawModel();

            /*VertexPositionColor[] vertices = new VertexPositionColor[] {
                new VertexPositionColor(new Vector3(-0.4f, -0.3f, 1.1f), XNAColor.White),
                new VertexPositionColor(new Vector3( 0.2f, -0.3f, 1.1f), XNAColor.White),
                new VertexPositionColor(new Vector3(    0,  0.3f, 1.1f), XNAColor.White),
            };

            BasicEffect effect = new BasicEffect(graphicsDevice);
            effect.VertexColorEnabled = true;
            effect.World = Matrix.CreateFromYawPitchRoll(0,0,0);
            effect.View = viewMatrix;
            effect.Projection = projectionMatrix;
            effect.CurrentTechnique.Passes[0].Apply();

            graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);*/
        }

        private void drawImage()
        {
            if (cameraImage == null) return;

            Texture2D texture = GraphicsUtils.GetTextureFromImage(cameraImage, graphicsDevice);

            spriteBatch.Begin(0, BlendState.Opaque);
            spriteBatch.Draw(
                texture,
                new Microsoft.Xna.Framework.Rectangle(
                    0, 0,
                    graphicsDevice.PresentationParameters.BackBufferWidth,
                    graphicsDevice.PresentationParameters.BackBufferHeight
                ),
                XNAColor.White
            );
            spriteBatch.End();

            graphicsDevice.BlendState = BlendState.Opaque;
            graphicsDevice.DepthStencilState = DepthStencilState.Default;
            graphicsDevice.RasterizerState = RasterizerState.CullNone;
            graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
        }

        private void drawModel()
        {
            if (model == null || markerTransform == null) return;

            Matrix scaleMatrix = Matrix.CreateScale(0.05f);
            Matrix rotationMatrix = Matrix.CreateRotationX((float)(Math.PI / 2)) * Matrix.CreateRotationZ((float)(-Math.PI / 2));
            Matrix translationMatrix = Matrix.CreateTranslation(0.05f, 0.05f, 0);

            Matrix transformMatrix = new Matrix(
                -(float)markerTransform[0, 0], (float)markerTransform[1, 0], (float)markerTransform[2, 0], 0,
                -(float)markerTransform[0, 1], (float)markerTransform[1, 1], (float)markerTransform[2, 1], 0,
                -(float)markerTransform[0, 2], (float)markerTransform[1, 2], (float)markerTransform[2, 2], 0,
                -(float)markerTransform[0, 3], (float)markerTransform[1, 3], (float)markerTransform[2, 3], 1
            );

            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);

            foreach (ModelMesh mesh in model.Meshes)
            {
                Matrix worldMatrix =
                    transforms[mesh.ParentBone.Index] *
                    Matrix.CreateScale(1 / mesh.BoundingSphere.Radius) *
                    scaleMatrix * rotationMatrix * translationMatrix * transformMatrix;

                foreach (Effect effect in mesh.Effects)
                {
                    applyTransforms(effect, worldMatrix);
                }

                mesh.Draw();
            }
        }

        private void applyTransforms(Effect effect, Matrix worldMatrix)
        {
            if (effect is BasicEffect)
            {
                BasicEffect basicEffect = (BasicEffect)effect;

                basicEffect.EnableDefaultLighting();

                basicEffect.World = worldMatrix;
                basicEffect.View = viewMatrix;
                basicEffect.Projection = projectionMatrix;
            }
            else
            {
                EffectParameter param = effect.Parameters["World"];
                if (param != null)
                {
                    param.SetValue(worldMatrix);
                }

                param = effect.Parameters["View"];
                if (param != null)
                {
                    param.SetValue(viewMatrix);
                }

                param = effect.Parameters["Projection"];
                if (param != null)
                {
                    param.SetValue(projectionMatrix);
                }
            }
        }
    }
}
